#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "papi.h" /* This needs to be included every time you use PAPI */

#define ERR -1
#define COL 1
#define ROW 2
#define NUM_EVENTS 1
#define ERROR_RETURN(retval) { fprintf(stderr, "Error %d %s:line %d: \n", retval,__FILE__,__LINE__);    exit(retval); }

int atoorder(char* arg);
double* alloc_matrix(int size);
void cache_reset();
void free_matrix(double* M);
void matrix_ones_init(double* M, int size);
void matrix_rand_init(double* M, int size);
void matrix_zero_init(double* M, int size);
void print_matrix(double* M, int size, int order);

int main(int argc, char** argv)
{
    int retval;

    /* Must be initialized to PAPI_NULL before calling PAPI_create_event */
    int EventSet = PAPI_NULL;

    char errstring[PAPI_MAX_STR_LEN];
    long long values[NUM_EVENTS];

    if (argc != 3)
    {
		fprintf(stdout, "Usage: %s [size] [order: col|row]\n", argv[0]);

		return 1;
    }

    int size = atoi(argv[1]);
	int order = atoorder(argv[2]);

	if (order == ERR)
	{
		fprintf(stdout, "Invalid order.\n\n");
		fprintf(stdout, "Usage: %s [size] [order: col|row]\n", argv[0]);
		
		return 1;
	}

    /* Allocate matrices spaces */
    double* A = alloc_matrix(size);

    /* Initialize matrices */
    matrix_ones_init(A, size);

    int i, j;

	//print_matrix(A, size, order);

    cache_reset();
	
	/****************************************************************************
     * This part initializes the library and compares the version number of the *
     * header file, to the version of the library, if these don't match then it *
     * is likely that PAPI won't work correctly. If there is an error, retval   *
     * keeps track of the version number.                                       *
     ****************************************************************************/
    if((retval = PAPI_library_init(PAPI_VER_CURRENT)) != PAPI_VER_CURRENT )
    {
        fprintf(stderr, "Error: %s\n", errstring);
        exit(1);
    }

    /* Creating event set */
    if ((retval=PAPI_create_eventset(&EventSet)) != PAPI_OK)
        ERROR_RETURN(retval);

	/* Add the array of events PAPI_TOT_INS and PAPI_TOT_CYC to the eventset */
    if ((retval=PAPI_add_event(EventSet, PAPI_L2_DCA)) != PAPI_OK)
        ERROR_RETURN(retval);
    
    /* Start counting */
    if ( (retval=PAPI_start(EventSet)) != PAPI_OK)
        ERROR_RETURN(retval);
        
    /* Add the array of events PAPI_TOT_INS and PAPI_TOT_CYC to the eventset */
    for (i=1; i<size; i++)
	{
		for (j=0; j<size-1; j++)
		{
			if (order == COL)
			{
				A[i+ j*size] = A[i + j*size] + A[(i-1) + (j+1)*size];
			}
			else if (order == ROW)
			{
				A[i*size + j] = A[i*size + j] + A[(i-1)*size + (j+1)];
			}
		}
	}

    /* Stop counting, this reads from the counter as well as stop it. */
    if ( (retval=PAPI_stop(EventSet,values)) != PAPI_OK)
        ERROR_RETURN(retval);

    double tot_access = 3.0 * (double)size * (double)size;
	double cache_misses = (double)values[0];
    double miss_ratio = cache_misses / tot_access;
    printf("%s\t%d\t%f\t%f\t%f\n", argv[2], size, miss_ratio, cache_misses, tot_access);
    
    if ( (retval=PAPI_remove_event(EventSet,PAPI_L2_DCA))!=PAPI_OK)
        ERROR_RETURN(retval);
    
    /* Free all memory and data structures, EventSet must be empty. */
    if ( (retval=PAPI_destroy_eventset(&EventSet)) != PAPI_OK)
        ERROR_RETURN(retval);
    
    /* Free the resources used by PAPI */
    PAPI_shutdown();

	//print_matrix(A, size, order);

    /* Free matrices */
    free_matrix(A);

	exit(0);
}

int atoorder(char* arg)
{
	if (strcmp(arg, "col") == 0)
	{
		return COL;
	}
	else if (strcmp(arg, "row") == 0)
	{
		return ROW;
	}

	return ERR;
}

double* alloc_matrix(int size)
{
    double* M = (double*) malloc(size * size * sizeof(double *));
    
    return M;
}

void cache_reset()
{
	double values[32000];

	int i;
	
	for(i = 0; i < 32000; i++)
	{
		values[i] = -1.0;
	}
}

void free_matrix(double* M)
{
    free(M);
}

void matrix_ones_init(double* M, int size)
{
    int i, j;
    
    for (i=0; i<size; i++)
    {
        for (j=0; j<size; j++)
        {
            M[i*size + j] = 1.0;
        }
    }
}

void matrix_rand_init(double* M, int size)
{
    int i, j;
    
    for (i=0; i<size; i++)
    {
        for (j=0; j<size; j++)
        {
            M[i*size + j] = rand()/123.0;
        }
    }
}

void matrix_zero_init(double* M, int size)
{
    int i, j;
    
    for (i=0; i<size; i++)
    {
        for (j=0; j<size; j++)
        {
            M[i*size + j] = 0.0;
        }
    }
}

void print_matrix(double* M, int size, int order)
{
    int i, j;
    
    for (i=0; i<size; i++)
    {
        for (j=0; j<size; j++)
        {
			if (order == COL)
			{
				printf("%f\t", M[j*size + i]);
			}
			else if (order == ROW)
			{
				printf("%f\t", M[i*size + j]);
			}
        }

        printf("\n");
    }
    
    printf("\n");
}
